/*
    搜索模块：负责整个搜索业务逻辑，对上层网络服务提供调用接口
    1、调用index模块创建索引
    2、根据上层请求关键词，jieba分词，在索引中检索分词，将结果序列化成json串，返回给上层
*/
#pragma once
#include <jsoncpp/json/json.h>
#include <map>
#include <algorithm>
#include <cctype>
#include "index.hpp"
#include "util.hpp"
#include "log.hpp"

namespace ns_searcher
{
    class Searcher // 搜索器
    {
    private:
    // 从content中提取摘要
    std::string getAbstract(const std::string& content, std::string&& word)
    {
        // 提取文档中含有关键词的字符串作为摘要，默认第一个关键字的前50，后100区间
        int pre_step = 100, post_step = 200;
        // 用关键字在正文中匹配也要忽略大小写
        auto it = std::search(content.begin(), content.end(), word.begin(), word.end(), [](const int& a, const int& b){
            return (tolower(a) == tolower(b));
        });
        if(it == content.end()) // 正文中没有出现关键词，默认获取前150个字符
        {
            int last = content.size() < 150 ? content.size() : 150;
            return content.substr(0, last);
        }

        int pos = std::distance(content.begin(), it);
        int begin = pos - pre_step, end = pos + post_step;
        if(begin < 0)
            begin = 0;
        if(end > content.size())
            end = content.size();
        return content.substr(begin, end-begin) + "...";
    }
    public:
        // 初始化搜索器
        void initSearcher(const std::string& filename)
        {
            // 获取单例索引
            _pindex = ns_index::Index::getInstance();

            LOG(DEBUG, "构建索引中......\n");
            // 构建索引
            _pindex->buildIndex(filename);
            LOG(DEBUG, "构建索引结束\n");
        }

        // 根据query搜索,将搜索结果转换成json字符串，通过pjson_str输出
        bool searchQuery(const std::string& query, std::string* pjson_str)
        {
            // 第一步：对query分词
            std::vector<std::string> tokens;
            ns_util::JiebaUtil::splitWord(query, &tokens);
            if(tokens.empty())
                return false;

            // 第二步：根据分词在索引中检索
            std::unordered_map<uint64_t, std::pair<int, int>> weightmap; // 统计文档的权重和标题权重（pair<文档权重，标题权重>）
            std::unordered_map<uint64_t, std::pair<std::string, int>> wordsmap; // 统计文档中出现的关键词权重最大的关键词
            std::unordered_map<uint64_t, std::vector<std::string>> debugmap; //文档中出现的所有关键词（测试用）
            for(auto& word : tokens) // 遍历每个关键词
            {
                boost::to_lower(word);
                // 检索：倒排索引中获取关键词为word的倒排拉链
                const ns_index::InvertedElemsList* plist = _pindex->invertedIndex(word);
                if(plist == nullptr)
                    continue;

                // 第三步：根据倒排拉链，统计相同id文档中所有匹配的关键词权重总和（视为文档权重）
                for(auto& e : *plist)
                {
                    // 统计文档权重
                    weightmap[e._id].first += e._weight;

                    // 统计标题权重
                    weightmap[e._id].second += e._wordfrecency._title_cnt;

                    // 统计文档中出现的关键词权重最大的关键词
                    if(wordsmap[e._id].second < e._weight)
                        wordsmap[e._id].first = std::move(word);

                    // 统计文档中出现的所有关键词（调试）
                    //debugmap[e._id].push_back(word);
                }
            }
            
            // 第四步：根据统计的结果，按照文档中所有关键字的总权重降序排列
            std::vector<std::pair<uint64_t, std::pair<int, int>>> resultsv;
            for(auto& e : weightmap)
                resultsv.push_back(std::move(e));
            std::sort(resultsv.begin(), resultsv.end(), [](const std::pair<uint64_t, std::pair<int, int>>& e1, const std::pair<uint64_t, std::pair<int, int>>& e2){
                if(e1.second.first > e2.second.first) return true;
                else if(e1.second.first == e2.second.first && e1.second.second > e2.second.second)
                    return true;
                return false;
            });
            // 调试
            //debugSortResult(resultsv);

            // 第五步：构建json串：遍历降序结果，根据文档id，在正排索引中索引到对应解析信息，构建输出json串
            Json::Value root, item;
            for(auto& e : resultsv) //依次处理每个文档
            {
                const ns_index::ForwardElem* pforwordelem = _pindex->forwardIndex(e.first); // 在正排索引中找到对应文档解析信息
                item["title"] = (pforwordelem->_docinfo)._title;
                item["abstract"] = getAbstract((pforwordelem->_docinfo)._content, std::move(wordsmap[e.first].first)); //从内容中提取摘要
                item["url"] = (pforwordelem->_docinfo)._url;
                // 调试
                //item["doc_weight"] = std::to_string(e.second.first);
                //item["title_weight"] = std::to_string(e.second.second);
                //item["words"] = debugWordsTOStr(debugmap[e->_id]);
                root.append(item);
            }
            Json::FastWriter fw;
            *pjson_str = fw.write(root);
            return true;
        }

        // // 调试代码
        // std::string debugWordsTOStr(const std::vector<std::string>& v)
        // {
        //     std::string ret;
        //     for(auto& e : v)
        //         ret += e;
        //     return ret;
        // }

        // // 调试代码
        // void debugSortResult(std::vector<std::pair<uint64_t, std::pair<int, int>>>& resultsv)
        // {
        //     for(auto& e : resultsv)
        //         std::cout << "文档id: " << e.first 
        //                 << ",文档权重: " << e.second.first 
        //                 << ",标题权重: " << e.second.second << "\n"; 
        // }
    private:
        ns_index::Index* _pindex;
    };
}